/*
 * Copyright 2007 National Bioinformatics Network
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dgbgui.modeldocument;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Observable;
import java.util.Set;

import queryexecute.CorbaExecutor;
import dgbgui.DGBGUI;
import dgbgui.modeldocument.primitives.Attribute;
import dgbgui.modeldocument.primitives.AttributeType;
import dgbgui.modeldocument.primitives.ConceptType;
import dgbgui.modeldocument.primitives.Conceptron;
import dgbgui.modeldocument.primitives.ConceptronType;
import dgbgui.modeldocument.primitives.DataType;
import dgbgui.modeldocument.primitives.Graph;
import dgbgui.modeldocument.primitives.LayerType;
import dgbgui.modeldocument.primitives.Primitive;
import dgbgui.modeldocument.primitives.RelationType;
import dgbgui.modeldocument.uimodels.DGBModelComboModel;




/**
 * @author bjnortier
 * Singleton & facade for canvas operations, i.e. crudding of query nodes etc.
 */
/**
 * @author bjnortier
 *
 */
/**
 * @author bjnortier
 *
 */
public class ModelDocument extends Observable  {

    
    public static enum ObservedEventType { 
	    QUERY_ADD,
        QUERY_REPLACE,
        QUERY_REMOVE,
        QUERY_RELABELLED,
        CLIPBOARD_CHANGED,
        QUERY_SUBMITTED,
        RESULT_RECEIVED,}
	
	public class ObservableData {
		
        public ObservableData(ObservedEventType type) {
            m_type = type;
        }
        
		public ObservableData(ObservedEventType type, Query node1, Query node2) {
            this(type);
			m_node1 = node1;
			m_node2 = node2;
		}
        
        public ObservableData(ObservedEventType type, Query node1) {
            this(type,node1,null);
        }
		
		public ObservedEventType getType() {
			return m_type;
		}
		
		public Query getNode() {
			return m_node1;
		}
		
		public Query getFromNode() {
			return m_node1;
		}
		
		public Query getToNode() {
			return m_node2;
		}
		
		private ObservedEventType m_type;
		private Query m_node1,m_node2;
		
	}
    
    // Private for singleton
    private ModelDocument() {
        // Clean model
    }
	
    public boolean typesInitialised() {
        return m_typesInitialised;
    }
    
    
	public void initialiseTypesAndModels() {
        
	    // Get the data types from the dgb
        DataType[] dataTypes =  CorbaExecutor.getDataTypes();
        if ( dataTypes != null ) {
            for ( DataType type : dataTypes ) {
                m_dataTypes.put(type.m_id,type);
            }
        }
        
        // If getting the data types failed, don't try and initialise the 
        // other types as well, since multiple error messages will be output
        
        if ( m_dataTypes.size() > 0 ) {
            m_typesInitialised = true;
            
            // Get the attribute types from the dgb
            AttributeType[] attributeTypes =  CorbaExecutor.getAttributeTypes();
            if ( attributeTypes != null ) {
                for ( AttributeType attributeType : attributeTypes ) {
                    m_attributeTypes.put(attributeType.m_id,attributeType);
                }
            }

            // Get the concept types from the dgb
            ConceptType[] conceptTypes =  CorbaExecutor.getConceptTypes();
            if ( conceptTypes != null ) {
                for ( ConceptType conceptType : conceptTypes ) {
                    m_conceptronTypes.put(conceptType.m_id,conceptType);
                }
            }

            // Get the relation types from the dgb
            RelationType[] relationTypes =  CorbaExecutor.getRelationTypes();
            if ( relationTypes != null ) {
                for ( RelationType relation : relationTypes ) {
                    m_conceptronTypes.put(relation.m_id,relation);
                }
            }

            // Get the layer types from the dgb
            LayerType[] layerTypes =  CorbaExecutor.getLayerTypes();
            if ( layerTypes != null ) {
                for ( LayerType layer : layerTypes ) {
                    m_conceptronTypes.put(layer.m_id,layer);
                }
            }
            
        }
        
        // Initialise the models. Done here and not in the "if"
        // above, so there's still a "No model" model if the connection
        // fails
        m_dgbModelsComboModel.updateModels(ModelFactory.getAllModels());
	}
	
	static public ModelDocument getInstance() {
		if (m_instance == null) {
			m_instance = new ModelDocument();
		}
		return m_instance;
	}
   
    
	public void clear() {
		
		// Notify before clearing
		for ( Query node : m_queries ) {
			setChanged();
			notifyObservers(new ObservableData(ObservedEventType.QUERY_REMOVE,node));
		}
		
		m_queries = new HashSet<Query>();
        setFileName(null);
        setClipBoard(new HashSet<Query>());
	}
	
	public void add(Query node) {
		assert (!m_queries.contains(node));
		m_queries.add(node);
		
		// Notify
		setChanged();
		notifyObservers(new ObservableData(ObservedEventType.QUERY_ADD,node));
	}
    
    public void add(Set<Query> queries) {
        // TODO: Make a set notification 
        for ( Query query : queries ) {
            add(query);
        }

    }
	
    public void remove(Query node) {
		assert (m_queries.contains(node));
		m_queries.remove(node);
		
		// Notify
		setChanged();
		notifyObservers(new ObservableData(ObservedEventType.QUERY_REMOVE,node));
	}
    
    public void remove(Set<Query> queries) {
        // TODO: Make a set notification 
        for ( Query query : queries ) {
            remove(query);
        }
    }
	
    public void replace(Query fromNode, Query toNode) {
		assert (m_queries.contains(fromNode));
		assert (!m_queries.contains(toNode));
		m_queries.remove(fromNode);
		m_queries.add(toNode);
		
		// Notify
		setChanged();
		notifyObservers(new ObservableData(ObservedEventType.QUERY_REPLACE,fromNode,toNode));
	}
    
    public void rename(Query query, String newLabel) {
        assert (m_queries.contains(query));
        
        query.setLabel(newLabel);
        // Notify
        setChanged();
        notifyObservers(new ObservableData(ObservedEventType.QUERY_RELABELLED,query));
    }
    

    public AttributeType getAttributeType(Attribute attr) {
        return m_attributeTypes.get(attr.m_id);
    }
    
    public Collection<AttributeType> getAttributeTypes() {
        return m_attributeTypes.values();
    }
    
    
    public ConceptronType getConceptronType(Conceptron instance) {
        return m_conceptronTypes.get(instance.m_typeID);
    }
    
    public Set<ConceptronType> getConceptronTypes(Class typeClass) {
        Set<ConceptronType> types = new HashSet<ConceptronType>();
        for ( ConceptronType type : m_conceptronTypes.values() ) {
            if ( typeClass.isInstance(type) ) {
                types.add(type);
            }
        }
        return types;
    }
    

    /**
     * @param type The conceptron type 
     * @return the identifier attribute type, null if no such type
     */
    public AttributeType getIdentifierAttributeType(ConceptronType type) {
        for ( AttributeType attrType : type.m_attributeTypes ) {
            if ( attrType.m_isIdentifier) {
                return attrType;
            }
        }
        return null;
    }
    
    
    /**
     * Get the attribtue names for the conceptron type
     * @param type
     * @return All the attribtue names for the conceptron type
     */
    public Set<String> getAttributeNames(ConceptronType type) {
        LinkedHashSet<String> attribNames = new LinkedHashSet<String>();
        for ( AttributeType attribType : type.m_attributeTypes) {
            attribNames.add(attribType.m_name);
        }
        return attribNames;
    }
    
    public Set<Query> getAllQueries() {
        return m_queries;
    }
    
    public Set<Query> getQueriesWithQL(String dgbQL) {
        Set<Query> queries = new HashSet<Query>();
        for ( Query query : m_queries ) {
            if ( query.getDefinition().getdgbQLString().equals(dgbQL) ) { 
                queries.add(query);
            }
        }
        return queries; 
    }
	
    public int size() {
		return m_queries.size();
	}
	
	public boolean contains(Query node) {
		return m_queries.contains(node);
	}
    
    /**
     * Get the graph that contains the conceptron. Returns null
     * if not found
     */
    public Graph getGraphContaining(Conceptron startConceptron) {
        for ( Query q : m_queries ) {
            QueryResult result = ResultStore.getInstance().getResult(q);
            for ( Primitive p : result.getPrimitives() ) {
                if ( p instanceof Graph) {
                    Graph g = (Graph)p;
                    if ( g.vertexSet().contains(startConceptron)) {
                        return g;
                    }
                }
            }
        }
        return null;
    }

    
    public ClassCounter getClassCounter() {
        return m_classCounter;
    }
    
    public void updateDGBModels() {
        m_models = ModelFactory.getAllModels();
        m_dgbModelsComboModel.updateModels(m_models);
    }
    
    public DGBModelComboModel getDGBModelComboModel() {
        return m_dgbModelsComboModel;
    }


    // ------------------- Serialization -------------
    
    /**
     * First serilization version
     */
    private static final long m_serialVersion = 1L;
    
    
    public void writeToStream(java.io.ObjectOutputStream stream) throws IOException {
        stream.writeLong(m_serialVersion);
        stream.writeObject(m_classCounter);
        stream.writeObject(m_dataTypes);
        stream.writeObject(m_attributeTypes);
        stream.writeObject(m_conceptronTypes);
        stream.writeObject(m_queries);
        stream.writeObject(m_models);
    }
    
    @SuppressWarnings("unchecked")
    public void readFromStream(java.io.ObjectInputStream stream)  throws IOException, ClassNotFoundException {
        long x = stream.readLong();
        assert(x == m_serialVersion);
        m_classCounter = (ClassCounter)stream.readObject();
        m_dataTypes = (Map<Long,DataType>)stream.readObject();
        m_attributeTypes = (Map<Long,AttributeType>)stream.readObject();
        m_conceptronTypes = (Map<Long,ConceptronType>)stream.readObject();
        Set<Query> queries = (Set<Query>)stream.readObject();
        add(queries);
        
        m_models = (Set<DGBModel>)stream.readObject();
        m_dgbModelsComboModel.updateModels(m_models);
    }
	
    
    // ----------------- Filename for save/load ------------
    
    public String getFileName() {
        return m_fileName;
    }
    
    public void setFileName(String fileName) {
        m_fileName = fileName;
        
        // Also set the window title 
        DGBGUI.getInstance().getMainFrame().setFilenameTitle(fileName);
    }
    
    
    // ----------------- The Clipboard -------------
    
    public void setClipBoard(Set<Query> queries) {
        m_clipBoard = queries;
        setChanged();
        notifyObservers(new ObservableData(ObservedEventType.CLIPBOARD_CHANGED));
    }
    
    public Set<Query> getClipBoard() {
        return m_clipBoard;
    }
    
	private static ModelDocument m_instance;
	// Use a set since element cannot be duplicated
	private Set<Query> m_queries = new HashSet<Query>();
    
    /// The clipboard
    private Set<Query> m_clipBoard = new HashSet<Query>();

    // A map of attribute type id to data types
    private Map<Long,DataType> m_dataTypes = new HashMap<Long,DataType>();
    // A map of attribute type id to atribute types
    private Map<Long,AttributeType> m_attributeTypes = new HashMap<Long,AttributeType>();
    // A map of concept type id to conceptron types
    private Map<Long,ConceptronType> m_conceptronTypes = new HashMap<Long,ConceptronType>();
    
    // The DGB models
    private Set<DGBModel> m_models = new HashSet<DGBModel>(); 
    // The combo model
    private DGBModelComboModel m_dgbModelsComboModel = new DGBModelComboModel();

    
    // The model filename
    String m_fileName = null;
    
    // The class counter (used for labels)
    ClassCounter m_classCounter = new ClassCounter();

    // Whether the types have been initialised
    boolean m_typesInitialised = false;







}
